package org.eraser.security.authentication.provider;

import org.eraser.security.authentication.oidc.EndUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyAuthoritiesMapper;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

/**
 * <p>
 * 认证管理器 AuthenticationManager 有很多实现，ProviderManager 是最常用的一个。
 * ProviderManager 是一个代理类，真正执行认证逻辑的是 AuthenticationProvider 的实现类，ProviderManager 会一一尝试这些实现直到有结果返回。
 * 如果一个 provider 认证失败会抛 AuthenticationException 异常。ProviderManager 就会去执行下一个。
 * <p>
 * 自定义 AuthenticationProvider
 * 这个类的身份验证方式：依据数据库中用户名和密码进行身份验证
 * 仿照 DaoAuthenticationProvider 实现
 */
@Service
public class EUDaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
    private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";

    @Autowired
    private PasswordEncoder passwordEncoder;

    private volatile String userNotFoundEncodedPassword;

    /**
     * GrantedAuthoritiesMapper 类的作用是将一个“授权集合(GrantedAuthority)”映射到另一个“授权集合” 将 UserDetails 拥有的“数据库层”的授权集合映射到 UsernamePasswordAuthenticationToken 中
     */
    private final GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    /**
     * 用于 ”角色继承的 GrantedAuthority“
     */
    private final GrantedAuthoritiesMapper roleHierarchyMapper = new RoleHierarchyAuthoritiesMapper(new RoleHierarchyImpl());

    /**
     * 用于 SimpleGrantedAuthority 的映射
     */
    private final GrantedAuthoritiesMapper simpleAuthorityMapper = new SimpleAuthorityMapper();

    @Autowired
    private EndUserService userDetailsService;

    public EUDaoAuthenticationProvider() {
        this(PasswordEncoderFactories.createDelegatingPasswordEncoder());
    }

    public EUDaoAuthenticationProvider(PasswordEncoder passwordEncoder) {
        this.setPasswordEncoder(passwordEncoder);
    }

    /**
     * 执行身份校验的逻辑
     * UsernamePasswordAuthenticationToken 中 principal：账号、credentials：密码、手机验证码
     */
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        if (authentication.getCredentials() == null) {
            this.logger.debug("Failed to authenticate since no credentials provided");
            throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
        } else {
            String presentedPassword = authentication.getCredentials().toString();
            if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
                this.logger.debug("Failed to authenticate since password does not match stored value");
                throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
            }
        }
    }

    /**
     * 检查 userDetailsService 是否被实例化
     */
    @Override
    protected void doAfterPropertiesSet() {
        Assert.notNull(this.userDetailsService, "A UserDetailsServiceImpl must be set");
    }

    /**
     * 检索用户信息
     */
    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        this.prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");
            } else {
                return loadedUser;
            }
        } catch (UsernameNotFoundException var4) {
            this.mitigateAgainstTimingAttack(authentication);
            throw var4;
        } catch (InternalAuthenticationServiceException var5) {
            throw var5;
        } catch (Exception var6) {
            throw new InternalAuthenticationServiceException(var6.getMessage(), var6);
        }
    }

    /**
     * 认证成功返回 authenticated=true 的 Authentication
     * 每次登录认证成功 数据库密文都会更新
     *
     * @param principal 用户账号
     */
    protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
        boolean upgradeEncoding = this.userDetailsService != null && this.passwordEncoder.upgradeEncoding(user.getPassword());

        if (upgradeEncoding) {
            String presentedPassword = authentication.getCredentials().toString();
            String newPassword = this.passwordEncoder.encode(presentedPassword);
            user = this.userDetailsService.updatePassword(user, newPassword);
        }
        UsernamePasswordAuthenticationToken result = UsernamePasswordAuthenticationToken.authenticated(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities()));
        result.setDetails(authentication.getDetails());
        this.logger.debug("Authenticated user");

        return result;
    }

    private void prepareTimingAttackProtection() {
        if (this.userNotFoundEncodedPassword == null) {
            this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
        }
    }

    /**
     * 防范定时攻击 即使认证失败也走一次加密比较
     */
    private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
        if (authentication.getCredentials() != null) {
            String presentedPassword = authentication.getCredentials().toString();
            this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
        }
    }

    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
        this.passwordEncoder = passwordEncoder;
        this.userNotFoundEncodedPassword = null;
    }

    protected UserDetailsService getUserDetailsService() {
        return this.userDetailsService;
    }

}
